<?php

/**
 * A Database abstracting class for the MoltenBlob web framework. This class should handle all db related and
 * be expanded with new features should something be amiss.
 * @author Marlon Brandon van der Linde
 * @copyright Marlon B van der Linde 2008
 * @version 1.00
 * @license http://www.opensource.org/licenses/mit-license.php MIT-License
*/
class MoltenBlobDB {

	static protected $db = null;
	protected $rows_affected = null;
	protected $last_error = null;

	/**
	 * Class Constructor
	 * @param string $user Database username
	 * @param string $pass Database password
 	 * @param string $host Database Hostname
	 * @param string $dbname Database Name
	 * @param string $driver Database driver(default=mysql)
	*/
	function __construct($user, $pass, $host, $dbname, $driver='mysql') {
		$dbdats = array(
			"user" => $user,
			"pass" => $pass,
			"host" => $host,
			"db" => $dbname,
			"driver" => $driver
		);

		if(!$this->fireUp($dbdats)) { return false; }
	}


	/**
	 * Class Destructor
	 */
	function __destruct() {
		$this->db = null;
	}


	/**
	 * Database ignition. Automatically called by the constructor
	 * @access protected
	 * @param array $dbdats
	 * @return bool True for success, false for failure
	 */
	protected function fireUp($dbdats) {
		try {
			$dbh = $dbdats['host'];
			$dbn = $dbdats['db'];
			$this->db = new PDO("mysql:host=$dbh;dbname=$dbn", $dbdats['user'], $dbdats['pass']);
			return true;
		}
		catch(PDOException $e) {
			$alert = "Database failure while connecting : " . $e->getMessage();
			echo '<br />' , $alert;
			return false;
		}
		return true;
	}


	/**
	 * Database driver check
	 * @access protected
	 * @todo - write and implement
	*/
	protected function checkDriver() {
	}


	/**
	 * Returns the number of affected rows by the last db related function
	 * @return int|bool
	*/
	function rows_affected() {
		if($this->rows_affected) {
			return $this->rows_affected;
		} else {
			return false;
		}
	}


	/**
	 * Returns the error message that was generated in this class
	 * @return string|bool
	*/
	function last_error() {
		if($this->last_error) {
			return $this->last_error;
		} else {
			return false;
		}
	}


	/**
	 * Gets all records matching the field(s) and value(s) supplied
	 * @param string $table The name of the table to operate on
	 * @param string $field The field to match
	 * @param string $value The value to find in $field
	 * @param int $limit Optional number of rows to LIMIT to
	 * @return mixed
	*/
	function getRows($table, $field, $value, $limit=null) {
		try {
			// PDO does not support field/table names afaict. So we have to do this...
			$this->scrub($table);
			$this->scrub($field);
			$query = "SELECT * FROM $table WHERE $field = :value";
			if($limit) { $query = $query . " LIMIT :limit"; }
			
			$okQuery = $this->db->prepare($query);
			//$okQuery->bindParam(':table', $table, PDO::PARAM_STR); 
			//$okQuery->bindParam(':field', $field, PDO::PARAM_STR);
			$okQuery->bindParam(':value', $value, PDO::PARAM_STR);
			if($limit) $okQuery->bindParam(':limit', $limit, PDO::PARAM_INT);

			$okQuery->execute();
			return $okQuery->fetchAll(PDO::FETCH_ASSOC);
		} catch (PDOException $e) {
			$this->last_error = "getRows(): " . $e->getMessage();
			return false;
		}
	}


	/**
	 * Inserts a new record into the specified table, using fields and values arrays.
	 * The two supplied arrays (fields/values) must have the same amount of elements.
	 * @todo Improve handling code by using ASSOC array instead for fields/values
	 * @param string $table The name of the table to insert into
	 * @param array $fields An array of fields coinciding with $values
	 * @param array $values An array of values to insert into the fields coinciding with $fields
	 * @return mixed
	*/
	function newRow($table, $fields, $values) {
		try {
			
			if ( count($fields) != count($values) ) {
				$this->last_error = "newRow(): fields/values array element count mismatch";
				return false;
			}

			// this needs to be refactored. Holy moly, I hate doing this...

			$this->scrub($table);
			$f = implode(',', $fields);
			$v = '\'' . implode('\',\'', $values) . '\'';
			
			$sql = "INSERT INTO $table ($f) VALUES ($v)";

			if($count = $this->db->exec($sql)) {
				$this->rows_affected = $count;
				return $this->db->lastInsertId();
			} else {
				$err = $this->db->errorInfo();
				$this->last_error = "PDO: " . $err[2];
				return false;
			}
		} catch (PDOException $e) {
			$this->last_error = "newRow(): " . $e->getMessage();
			return false;
		}
	}


	/**
	 * Update an existing row, using the fields and values arrays.
	 * As with newRow(), the field/value counts must be the same
	 * This is a dumb function, and will return false on failures. No checks are
	 * currently made for mandatory fields, false will simply be thrown back.
	 * @param string $table The name of the table to update
	 * @param array $fields Array of fields to update
	 * @param array $values Array of values to insert into $fields
	 * @param int $id The id of the row to affect changes to
	 * @return int ID of the affected row
	 */
	function bumpRow($table, $fields, $values, $id) {
		try {
			if ( count($fields) != count($values) ) {
				$this->last_error = "newRow(): fields/values array element count mismatch";
				return false;
			}
			
			$this->scrub($table);
			$f = $fields;
			$v = $values;

			// build an AND list depending on the number of fields provided
			$ands = count($f);
			$i = 0;
			$andList = " SET $f[$i]='$v[$i]' ";
			$i++;
			while($i != $ands) {
				$andList .= ", $f[$i]='$v[$i]' "; 
				$i++;
			}

			$sql = "UPDATE $table $andList WHERE id = $id";

			$prep = $this->db->prepare($sql);
			if($prep->execute()) {
				return true;
			}
			return false;
		} catch (PDOException $e) {
			$this->last_error = "bumpRow(): " . $e->getMessage();
			return false;
		}
	}



	/**
	 * Deletes a specific row matching the field(s) and value(s)
	 * This function may delete one or many records depending on the match. No LIMIT is imposed.
	 * @param string $table The table to delete the record/s from
	 * @param string $field The field to check for match
	 * @param string $value The value to look for in $field
	 * @param string $field2 Optional and additional matching field
	 * @param string $value2 Optional and additional value for $field2
	 * @return mixed
	*/
	function nukeRows($table, $field, $value, $field2=null, $value2=null) {
		try {
			$this->scrub($table);
			$this->scrub($field);
			$query = "DELETE FROM $table WHERE $field = :value";
			if( $field2 && $value2 ) {
				$this->scrub($field2);
				$query = $query . " AND $field2 = :value2";
			}
			$okQuery = $this->db->prepare($query);
			//$okQuery->bindValue(':table', $table);
			//$okQuery->bindValue(':field', $field);
			$okQuery->bindValue(':value', $value);
			if( $field2 && $value2 ) {
				//$okQuery->bindValue(':field2', $field2);
				$okQuery->bindValue(':value2', $value2);
			}
			$okQuery->execute();
			$this->rows_affected = $okQuery->rowCount();
			return $this->rows_affected;
		} catch (PDOException $e) {
			$this->last_error = "nukeRows(): " . $e->getMessage();
			return false;
		}
	}


	/**
	 * Create


	/**
	 * Return array of tables in this database
	 * @return array
	*/
	function getTables() {
		try {
			$s = $this->db->prepare("SHOW TABLES");
			$s->execute();
			return $s->fetchAll(PDO::FETCH_COLUMN);
		} catch(PDOException $e) {
			$this->last_error = "getTables(): " . $e->getMessage();
			return false;
		}
	}


	/**
	 * Optimize a database table
	 * @see getTables()
	 * @param string $table Table name to optimize [could be obtained from getTables()]
	 * @return bool
	*/ 
	function optimizeTable($table) {
		$this->scrub($table);
		$query = "OPTIMIZE TABLE $table";
		try {
			$ok = $this->db->prepare($query);
			//$ok->bindValue(':table', $table);
			$ok->execute();
			return true;
		} catch (PDOException $e) {
			$this->last_error = "optimizeTable(): " . $e->getMessage();
			return false;
		}

	}


	/**
	 * As PDO/MySQL is preventing us from binding table and field name values,
	 * it appears that we need to clean incoming variables alot more. 
	 * Can't _really_ use mysql_real_escape_string() so this is it :)
	 * @param string $dirty A REFERENCE to the string being cleaned
	 * @access protected
	 */
	protected function scrub(&$dirty) {
		//$dirty = mysql_real_escape_string($dirty);
		$dirty = strtr($dirty, array(
    		"\x00" => '\x00',
    		"\n" => '\n',
    		"\r" => '\r',
    		'\\' => '\\\\',
    		"'" => "\'",
    		'"' => '\"',
    		"\x1a" => '\x1a'
    	));
	}


}

?>
